using System;
using System.Collections.Generic;
using System.Text;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * An element for the unified macro/transform syntax. 
     */

    internal sealed class UnifiedCall : TemplateElement
    {
        private readonly Expression _nameExp;
        private readonly Dictionary<string, Expression> _namedArgs;
        private readonly List<Expression> _positionalArgs;
        private readonly List<string> _bodyParameterNames;
        internal bool LegacySyntax;

        internal UnifiedCall(
            Expression nameExp,
            Dictionary<string, Expression> namedArgs,
            TemplateElement nestedBlock,
            List<string> bodyParameterNames)
        {
            _nameExp = nameExp;
            _namedArgs = namedArgs;
            NestedBlock = nestedBlock;
            _bodyParameterNames = bodyParameterNames;
        }

        internal UnifiedCall(
            Expression nameExp,
            List<Expression> positionalArgs,
            TemplateElement nestedBlock,
            List<string> bodyParameterNames)
        {
            _nameExp = nameExp;
            _positionalArgs = positionalArgs;
            if (nestedBlock == TextBlock.EmptyBlock)
            {
                nestedBlock = null;
            }
            NestedBlock = nestedBlock;
            _bodyParameterNames = bodyParameterNames;
        }

        internal override void Accept(Environment env)
        {
            ITemplateModel tm = _nameExp.GetAsTemplateModel(env);
            if (tm == Macro.DoNothingMacro) return; // shortcut here.
            if (tm is Macro)
            {
                var macro = (Macro) tm;
                if (macro.IsFunction && !LegacySyntax)
                {
                    throw new TemplateException(
                        "Routine " + macro.GetName() +
                        " is a function. A function can only be called " +
                        "within the evaluation of an expression.", env);
                }
                env.Visit(macro, _namedArgs, _positionalArgs, _bodyParameterNames,
                          NestedBlock);
            }
            else
            {
                bool isDirectiveModel = tm is ITemplateDirectiveModel;
                if (isDirectiveModel || tm is ITemplateTransformModel)
                {
                    IDictionary<string, ITemplateModel> args;
                    if (_namedArgs != null && _namedArgs.Count != 0)
                    {
                        args = new Dictionary<string, ITemplateModel>();
                        foreach (KeyValuePair<string, Expression> namedArg in _namedArgs)
                        {
                            string key = namedArg.Key;
                            Expression valueExp = namedArg.Value;
                            ITemplateModel value = valueExp.GetAsTemplateModel(env);
                            args.Add(key, value);
                        }
                    }
                    else
                    {
                        args = EmptyMap<string, ITemplateModel>.Instance;
                    }
                    if (isDirectiveModel)
                    {
                        env.Visit(NestedBlock, (ITemplateDirectiveModel) tm, args,
                                  _bodyParameterNames);
                    }
                    else
                    {
                        env.Visit(NestedBlock, (ITemplateTransformModel) tm, args);
                    }
                }
                else if (tm == null)
                {
                    throw new InvalidReferenceException(
                        GetStartLocation() + " " + _nameExp + " not found.", env);
                }
                else
                {
                    throw new TemplateException(
                        GetStartLocation() + ": " + _nameExp +
                        " is not a user-defined directive. It is a " +
                        tm.GetType().FullName, env);
                }
            }
        }

        public override string GetCanonicalForm()
        {
            var buf = new StringBuilder("<@");
            buf.Append(_nameExp.GetCanonicalForm());
            if (_positionalArgs != null)
            {
                for (int i = 0; i < _positionalArgs.Count; i++)
                {
                    Expression arg = _positionalArgs[i];
                    if (i != 0)
                    {
                        buf.Append(',');
                    }
                    buf.Append(' ');
                    buf.Append(arg.GetCanonicalForm());
                }
            }
            else
            {
                var keys = new List<string>(_namedArgs.Keys);
                keys.Sort();
                foreach (string t in keys)
                {
                    Expression arg = _namedArgs[t];
                    buf.Append(' ');
                    buf.Append(t);
                    buf.Append('=');
                    buf.Append(arg.GetCanonicalForm());
                }
            }
            if (NestedBlock == null)
            {
                buf.Append("/>");
            }
            else
            {
                buf.Append('>');
                buf.Append(NestedBlock.GetCanonicalForm());
                buf.Append("</@");
                if (_nameExp is Identifier || (_nameExp is Dot && ((Dot) _nameExp).OnlyHasIdentifiers()))
                {
                    buf.Append(_nameExp);
                }
                buf.Append('>');
            }
            return buf.ToString();
        }

        public override string GetDescription()
        {
            return "user-directive " + _nameExp;
        }

        /*
        //REVISIT
        boolean heedsOpeningWhitespace() {
            return nestedBlock == null;
        }

        //REVISIT
        boolean heedsTrailingWhitespace() {
            return nestedBlock == null;
        }*/
    }
}